package com.rgs.gateway.web.common.filter;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.rgs.gateway.authentication.feignclient.client.AuthFeignClient;
import com.rgs.gateway.common.enums.SignTypeEnum;
import com.rgs.gateway.common.enums.SystemErrorEnum;
import com.rgs.gateway.common.pojo.Result;
import com.rgs.gateway.web.common.config.value.IgnoreSignUrlConfig;
import com.rgs.gateway.web.common.design.sign.factory.GatewaySignFactory;
import com.rgs.gateway.web.common.design.sign.strategy.GatewaySignStrategy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.cloud.gateway.support.CachedBodyOutputMessage;
import org.springframework.cloud.gateway.support.DefaultServerRequest;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.util.CollectionUtils;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.*;

/**
 * @author RenGaoshuai
 * @date 2024/7/1 14:48
 * @description 签名过滤器
 */
@Configuration
@Slf4j
@Order(2)
public class AccessSignFilter implements GlobalFilter, GatewayFilter {
    /**
     * 请求头变量名公钥
     */
    private static final String APP_KEY = "appKey";
    /**
     * 用户是否需要验签（0：否 1：是）
     */
    private static final String ISSIGN = "isSign";
    /**
     * 用户的验签类型
     */
    private static final String SIGNTYPE = "signType";


    /**
     * 用户的密钥
     */
    protected static final String APP_SECRET = "appSecret";

    /**
     * 请求头变量名 SIGN
     */
    protected static final String SIGN = "sign";

    /**
     * 请求头变量名 TIMESTAMP
     */
    protected static final String TIMESTAMP = "timestamp";

    private static final String CONTENT_TYPE = "Content-Type";

    /**
     * 签名验证时间（TIMES =分钟 * 秒 * 毫秒）
     * 当前设置为：5分钟有效期
     */
    protected static final Integer VALIDTIMES = 5 * 60 * 1000;

    @Autowired
    private IgnoreSignUrlConfig ignoreSignUrlConfig;
    @Autowired
    private AuthFeignClient authFeignClient;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String method = request.getMethodValue();
        String contentType = request.getHeaders().getFirst(CONTENT_TYPE);

        // Multipart上传格式 DELETE删除接口不需要验签，直接放行
        boolean isMultipartType = StrUtil.isNotBlank(contentType) && contentType.contains(MediaType.MULTIPART_FORM_DATA_VALUE);
        boolean isDeleteType = HttpMethod.DELETE.name().equalsIgnoreCase(method);
        if (isMultipartType || isDeleteType) {
            return chain.filter(exchange);
        }

        String url = request.getPath().value();

        if (!CollectionUtils.isEmpty(ignoreSignUrlConfig.getIgnoreSignUrls()) && ignoreSignUrlConfig.getIgnoreSignUrls().contains(url)) {
            return chain.filter(exchange);
        }

        //获取账户信息
        String appKey = request.getHeaders().getFirst(APP_KEY);
        String timestamp = request.getHeaders().getFirst(TIMESTAMP);

        //获取账户信息
        Result authUserResult = authFeignClient.authUserInfo(appKey);
        if (authUserResult.isFail() || Objects.isNull(authUserResult.getData())) {
            log.error("调用鉴权服务查询账户信息返回失败");
            return unauthorized(exchange, Result.fail(SystemErrorEnum.UNAUTHORIZED));
        }

        Map<String, Object> authMap = (Map<String, Object>) authUserResult.getData();

        Integer isSign = MapUtil.getInt(authMap, ISSIGN);
        Integer signType = MapUtil.getInt(authMap, SIGNTYPE);
        String appSecret = MapUtil.getStr(authMap, APP_SECRET);

        String sign = request.getHeaders().getFirst(SIGN);

        //验签结果
        List<Result> resultList = new ArrayList<>();
        if (Objects.equals(isSign, 1)) {
            if (StrUtil.isBlank(sign)) {
                return invalidSign(exchange, Result.fail(SystemErrorEnum.ARGUMENT_NOT_VALID));
            }
            // 入参报文
            Map<String, Object> params = new HashMap<>();
            request.getQueryParams().forEach((key, items) -> params.put(key, items.get(0)));

            if (StrUtil.equals(method, HttpMethod.GET.name())) {
                Result checkSignResult = checkSign(params, sign, appKey, appSecret, signType, timestamp);
                if (checkSignResult.isFail()) {
                    return invalidSign(exchange, checkSignResult);
                }
            }

            Mono<String> modifiedBody = null;
            if (StrUtil.equals(method, HttpMethod.POST.name())) {
                if (StrUtil.equals(contentType, MediaType.APPLICATION_JSON_VALUE)) {
                    ServerRequest serverRequest = new DefaultServerRequest(exchange);
                    String finalAppKey = appKey;

                    modifiedBody = serverRequest.bodyToMono(String.class).flatMap(body -> {
                        log.info("接口入参报文信息为：{} ", body);

                        Map<String, Object> jsonMap = isJsonString(body);
                        //判断入参报文是否是json格式
                        if (!MapUtil.getBool(jsonMap, "isJsonString")) {
                            throw new RuntimeException(SystemErrorEnum.NOT_VALID.getMessage());
                        }
                        //接口入参报文信息
                        params.putAll(MapUtil.get(jsonMap, "params", Map.class));

                        Result checkSignResult = checkSign(params, sign, finalAppKey, appSecret, signType, timestamp);
                        resultList.add(checkSignResult);

                        return Mono.just(body);
                    });
                }

                //验签
                if (StrUtil.isBlank(contentType) && MapUtil.isNotEmpty(params)) {
                    Result checkSignResult = checkSign(params, sign, appKey, appSecret, signType, timestamp);
                    if (checkSignResult.isFail()) {
                        return invalidSign(exchange, checkSignResult);
                    }
                }

                //POST-JSON请求验签
                if (Objects.equals(MediaType.APPLICATION_JSON_VALUE, contentType)) {
                    return cacheBody(exchange, chain, modifiedBody, resultList);
                }


            }

        }
        return chain.filter(exchange);
    }

    /**
     * 网关拒绝,返回信息
     *
     * @param
     */
    private Mono<Void> unauthorized(ServerWebExchange serverWebExchange, Result result) {
        serverWebExchange.getResponse().setStatusCode(HttpStatus.valueOf(result.getCode()));
        DataBuffer buffer = serverWebExchange.getResponse().bufferFactory().wrap(JSONUtil.toJsonStr(result).getBytes());
        return serverWebExchange.getResponse().writeWith(Flux.just(buffer));
    }

    /**
     * 无效验签,返回信息
     *
     * @param
     */
    private Mono<Void> invalidSign(ServerWebExchange serverWebExchange, Result result) {
        serverWebExchange.getResponse().setStatusCode(HttpStatus.valueOf(result.getCode()));
        DataBuffer buffer = serverWebExchange.getResponse().bufferFactory().wrap(JSONUtil.toJsonStr(result).getBytes());
        return serverWebExchange.getResponse().writeWith(Flux.just(buffer));
    }

    /**
     * 判断入参报文是否是json格式
     *
     * @param content
     * @return
     */
    private Map<String, Object> isJsonString(String content) {
        Map<String, Object> jsonMap = new HashMap<>();
        try {
            JSONObject jsonObject = JSONUtil.parseObj(content);
            jsonMap.put("isJsonString", true);
            jsonMap.put("params", jsonObject);
        } catch (Exception e) {
            jsonMap.put("isJsonString", false);
        }
        return jsonMap;
    }

    /**
     * 对request的body修改处理
     *
     * @param exchange
     * @param chain
     * @param modifiedBody
     * @return
     */
    private Mono<Void> cacheBody(ServerWebExchange exchange, GatewayFilterChain chain, Mono<String> modifiedBody, List<Result> resultList) {
        BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
        HttpHeaders headers = new HttpHeaders();
        headers.putAll(exchange.getRequest().getHeaders());
        headers.remove(HttpHeaders.CONTENT_LENGTH);
        CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
        return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> {
            ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
                @Override
                public HttpHeaders getHeaders() {
                    long contentLength = headers.getContentLength();
                    HttpHeaders httpHeaders = new HttpHeaders();
                    httpHeaders.putAll(super.getHeaders());
                    if (contentLength > 0) {
                        httpHeaders.setContentLength(contentLength);
                    } else {
                        httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
                    }
                    return httpHeaders;
                }

                @Override
                public Flux<DataBuffer> getBody() {
                    return outputMessage.getBody();
                }
            };
            if (!CollectionUtils.isEmpty(resultList) && resultList.get(0).isFail()) {
                return invalidSign(exchange, resultList.get(0));
            }
            return chain.filter(exchange.mutate().request(decorator).build());
        }));
    }


    /**
     * 校验验签信息
     *
     * @param params 参数
     * @param sign 携带的签名
     * @param appSecret 密钥
     * @param signType 签名方式
     * @return
     */
    private Result checkSign(Map<String, Object> params, String sign, String appKey, String appSecret, Integer signType, String timeStamp) {
        log.info("签名校验参数集合：{}", params);
        if (StrUtil.isNotBlank(timeStamp)) {
            params.put(TIMESTAMP, timeStamp);
        }

        //时间戳必填
        if (StrUtil.isBlank(timeStamp) && (Objects.equals(signType, SignTypeEnum.DATACENTER.getCodeValue()) || Objects.equals(signType, SignTypeEnum.SIMUAI.getCodeValue()) || Objects.equals(signType, SignTypeEnum.SIMU.getCodeValue()))) {
            return Result.fail(SystemErrorEnum.NOT_FOUND_TIMESTAMP);
        }

        //策略模式获取sign
        GatewaySignStrategy strategy = GatewaySignFactory.getInstance().getSignType(signType);
        if (Objects.isNull(strategy)) {
            return Result.fail(SystemErrorEnum.SIGN_TYPE_NOT_FUND);
        }
        String localSign = strategy.getSign(appKey, appSecret, timeStamp, params);

        if (!StrUtil.equals(sign, localSign)) {
            return Result.fail(SystemErrorEnum.NOT_MATCH_AUTH_RULE);
        }
        // 校验签名是否失效
        if (params.containsKey(TIMESTAMP)) {
            long thisTime = System.currentTimeMillis() - Long.valueOf(String.valueOf(params.get(TIMESTAMP)));
            log.info("校验签名是否有效的时间：{}", thisTime);
            // 比对时间是否失效
            if (thisTime > VALIDTIMES) {
                return Result.fail(SystemErrorEnum.ARGUMENT_NOT_VALID);
            }
        }
        return Result.success();
    }


}
